#include <dicom/storage/encrypt.h>
#include <dicom/storage/decrypt.h>
#include <gwbase/base/util.h>
#include <gwbase/base/test_harness.h>
#include <gwbase/base/test_util.h>
#include <surbasic/surbasic.h>
#include <dicom/upload/cloud_uploader.h>
#include <dicom/file_object.h>
#include <dicom/upload/same_hash_checker.h>

using namespace dicom;
using namespace SDBasic;
using namespace SDBasic::code;

class CloudUploaderTest {};

static const char* user = "surmd_test_1@163.com";
static const char* pwd = "aA111111";

FileObject DummyFileObject(int n) {
  FileObject obj;
  obj.local_id = StringPrintf("local_id_%d", n);
  obj.name = StringPrintf("name_%d.txt", n);
  obj.modifiedTime = dicom::Timestamp::Now();
  obj.hex_key = dicom::ByteArray::genRandom(32).toHexStr();
  obj.digest = "d41d8cd98f00b204e9800998ecf8427e";
  return obj;
}

uint64_t GetToken(const std::string& user, const std::string& password,
                  AccessToken* token) {
  uint64_t key =
      SDBasic::Login("https://open.surdoc.net", user, password, *token);
  EXPECT_TRUE(key > 0);
  return key;
}

static void PrintSameHashList(const ServerHashEntryList* list) {
  printf("---------------%ld Same Hash Info-----------\n", list->size());
  for (size_t i = 0; i < list->size(); ++i) {
    const ServerHashEntry& n = (*list)[i];
    printf("HEXKEY: %s; ENC_HASH: %s; SIZE: %ld; ENC_SIZE: %ld\n",
           n.hexkey.c_str(), n.enc_digest.c_str(), n.src_size, n.enc_size);
  }
}

const char* digestOfEmptyFile =
    "d41d8cd98f00b204e9800998ecf8427eda39a3ee5e6b4b0d3255bfef95601890afd807090";

const char* sha512OfEmptyFile =
    "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d"
    "85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";

void GenerateServerHashEntryList(ServerHashEntryList* list, int count) {
  const char* src_file = "_server_hash_list_src.dat";
  const char* enc_file = "_server_hash_list_enc.dat";

  for (int i = 0; i < count; ++i) {
    HashEntry h;
    std::string key = dicom::ByteArray::genRandom(32).toHexStr();
    // this is the one returned from server
    if (i == 0) {
      key = "d34b967f80528914d069c0a399a35ca30d9205601e3660baa30b7da3e0320fb3";
    }
    Status s = dicom::ZipAndEncryptFile(key, src_file, enc_file, &h);
    EXPECT_TRUE(s.ok());
    ServerHashEntry e;
    e.enc_size = h.enc_size;
    e.enc_digest = h.enc_digest;
    e.src_size = h.src_size;
    e.hexkey = h.hexkey;
    list->push_back(e);
  }
}

FileObject SaveFileToCache(LRUFileCache* fcache, HashCache* hcache,
                           const std::string& hexkey, const std::string& path,
                           int n, HashEntry* hash, std::string* enc_file) {
  FileObject fo = DummyFileObject(n);
  fo.hex_key = hexkey;

  LRUFileCache::Handle* h =
      dicom::SaveFileToCacheWithAES(fcache, path, fo.local_id, hexkey, hash);

  EXPECT_TRUE(h != NULL);
  *enc_file = fcache->FilePath(h);
  fcache->Release(h);

  hash->status = HASH_LOCAL;
  hcache->UpdateHashEntry(hash);
  return fo;
}

TEST(CloudUploaderTest, CheckRapidUpload) {
  AccessToken token;
  uint64_t surdoc_key = GetToken(user, pwd, &token);

  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);
  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  LRUFileCache* cache = dicom::NewLocalCache("_test_dir", 1024 * 1024 * 1024);

  // Get list from server
  SameHashChecker u(&c);
  ServerHashEntryList list;
  u.GetServerHashEntryListFromServer(surdoc_key, digestOfEmptyFile,
                                     sha512OfEmptyFile, &list);

  PrintSameHashList(&list);
  bool rapid = false;

  const char* src_file = "src_file.dat";
  CreateTestFile(src_file, 0, false);
  HashEntry hash2;
  rapid = u.CanRapidUploadSlowCheck(&list, src_file, &hash2);
  EXPECT_TRUE(rapid);

  CloudUploader up(cache, &c);
  std::string enc_file;

  // Now encryt file with same hexkey
  HashEntry hash3, hash3up;
  FileObject fo3 =
      SaveFileToCache(cache, &c, hash2.hexkey, src_file, 1, &hash3, &enc_file);
  s = up.CheckRapidUpload(surdoc_key, fo3, enc_file, &rapid, &hash3up);
  EXPECT_TRUE(s.ok());
  EXPECT_TRUE(rapid);
  EXPECT_TRUE(hash3up.hexkey == hash3.hexkey);

  // Now encryt file with random hexkey
  HashEntry hash4, hash4up;
  FileObject fo4 =
      SaveFileToCache(cache, &c, dicom::ByteArray::genRandom(32).toHexStr(),
                      src_file, 1, &hash4, &enc_file);
  s = up.CheckRapidUpload(surdoc_key, fo4, enc_file, &rapid, &hash4up);
  EXPECT_TRUE(s.ok());
  EXPECT_TRUE(rapid);
  EXPECT_TRUE(hash4up.hexkey != hash4.hexkey);
}

TEST(CloudUploaderTest, RapidUpload) {
  AccessToken token;
  uint64_t surdoc_key = GetToken(user, pwd, &token);

  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);
  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  LRUFileCache* cache = dicom::NewLocalCache("_test_dir", 1024 * 1024 * 1024);

  // Get list from server
  SameHashChecker u(&c);
  ServerHashEntryList list;
  u.GetServerHashEntryListFromServer(surdoc_key, digestOfEmptyFile,
                                     sha512OfEmptyFile, &list);

  PrintSameHashList(&list);

  bool rapid;

  const char* src_file = "src_file.dat";
  CreateTestFile(src_file, 0, false);
  HashEntry hash2;
  rapid = u.CanRapidUploadSlowCheck(&list, src_file, &hash2);
  hash2.status = HASH_LOCAL;
  EXPECT_TRUE(rapid);

  CloudUploader up(cache, &c);

  // Now encryt file with same hexkey
  std::string enc_file;
  HashEntry hash3, hash3up;
  FileObject fo3 =
      SaveFileToCache(cache, &c, hash2.hexkey, src_file, 1, &hash3, &enc_file);
  s = up.CheckRapidUpload(surdoc_key, fo3, enc_file, &rapid, &hash3up);
  EXPECT_TRUE(s.ok());
  EXPECT_TRUE(rapid);
  EXPECT_TRUE(hash3up.hexkey == hash3.hexkey);

  SDBasic::tree::TreeEntity node;
  s = up.DoRapidUpload(token, fo3, "0", &hash3up, &node);
  EXPECT_TRUE(s.ok());
}

TEST(CloudUploaderTest, DoUploadSmallFile) {
  AccessToken token;
  GetToken(user, pwd, &token);

  const char* dbpath = "_hash_cache_test.db";
  dicom::DeleteFile(dbpath);
  dicom::HashCache c;
  Status s = c.Open(dbpath);
  EXPECT_TRUE(s.ok());

  LRUFileCache* cache = dicom::NewLocalCache("_test_dir", 1024 * 1024 * 1024);

  const char* src_file = "src_file.dat";
  CreateTestFile(src_file, 128, true);
  CloudUploader up(cache, &c);

  std::string enc_file;
  std::string key = dicom::ByteArray::genRandom(32).toHexStr();
  HashEntry hash3, hash3up;
  FileObject fo3 =
      SaveFileToCache(cache, &c, key, src_file, 1, &hash3, &enc_file);

  SDBasic::tree::TreeEntity node;
  s = up.DoUploadSmallFile(token, fo3, "0", enc_file, &hash3, &node);
  EXPECT_TRUE(s.ok());
}

int main() {
  dicom::RunAllTests();
  return 0;
}
